#include"definicje.h"
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netdb.h>
#include<pthread.h>
#include<unistd.h>
#include"protokol.h"
#include"komunikator.h"
#include"katalogi.h"
#include"ssl.h"
#include<openssl/ssl.h>
#include<openssl/bio.h>
#include<openssl/err.h>


#define PORT "9999"

struct daneWatku
{
   struct komunikator *komunikator;
};

int wyslijPZ(struct komunikator *k, uint8_t operacja, uint32_t ktory, uint32_t ktoryTam)
{
   struct pakietACK *ack;
   int wynik;

   ack=(struct pakietACK *)malloc(rozmiarACK);
   /* no i gdzies free */
   if(ack==NULL)
   {
      fprintf(stderr,"Nie można zaalokować pamięci\n");
      return -1;
   }
   ack->naglowek.rozmiar=htons((uint16_t)4u);
   ack->naglowek.operacja=operacja;
   ack->naglowek.opcje=0u;
   ack->naglowek.ktory=htonl((uint32_t)ktory);
   ack->ktory=htonl((uint32_t)ktoryTam);
   wynik=wyslijKomunikat(k,NULL,(int8_t *)ack);
   if(wynik==-1)
   {
      return -1;
   }
   return 0;
}

void *watek(void *d)
{
   /* Lista rzeczy które należy dodać:
    * 1. Powtórna próba odebrania w wypadku odebrania
    * błędnego pakietu (także w kliencie)
    * 2. Wysłanie wskaźnika pustego na socket oznacza zakończenie
    * wątku i prośbę o usunięcie gniazda po tamtej stronie
    * 3. Dodać timeout na sockety i obsługę w kodzie
    */
   uint32_t ktory=0;
   uint32_t ktoryTam=0;
   int16_t rozmiar=100;
   int8_t *bufor;
   struct sockaddr_storage *adres;
   struct naglowek *nag;
   struct pakietACK *ack;
   struct pakietSET *set;
   int8_t *dane;
   int8_t *aktualne;
   struct daneWatku *dw=(struct daneWatku *)d;
   struct elementListy *element;
   extern struct elementListy *katalogi;

   int8_t *napis;
   FILE* plik;
   long dlugoscPliku;
   size_t zapisane;
   int wynik;

fprintf(stderr,"test\n");

//#ifdef DEBUG
   fprintf(stderr,"Watek czeka na komunikat\n");
//#endif
   wynik=przyjmijKomunikat(dw->komunikator,&adres,&bufor);
   if(wynik==-1)
   {
      pthread_exit(NULL);
   }
   ++ktoryTam;

#ifdef DEBUG
   fprintf(stderr,"Watek otrzymal komunikat\n");
#endif
   set=(struct pakietSET *)bufor;

   if(set->naglowek.operacja==OP_SET && set->naglowek.rozmiar==htons((uint16_t)2u))
   {
#ifdef DEBUG
   fprintf(stderr,"Ustawiam rozmiar na %hu\n",ntohs(set->rozmiar));
#endif
      rozmiar=ntohs(set->rozmiar);
      free(bufor);
   }
   else
   {
      free(bufor);
      fprintf(stderr,"Błędna operacja. Oczekiwano operacji SET.\n");
      pthread_exit(NULL);
   }
#ifdef DEBUG
   fprintf(stderr,"Watek wysyla potwierdzenie\n");
   fprintf(stderr,"ktory=%u,ktoryTam=%u\n");
#endif
   wynik=wyslijPZ(dw->komunikator,OP_ACK,ktory,ktoryTam-1);
   if(wynik==-1)
   {
      pthread_exit(NULL);
   }
   ++ktory;
   wynik=przyjmijKomunikat(dw->komunikator,&adres,&bufor);
   if(wynik==-1)
   {
      pthread_exit(NULL);
   }
   ++ktoryTam;
   nag=(struct naglowek *)bufor;
   dane=bufor+rozmiarNaglowka;

   if(nag->operacja==OP_LIST && nag->rozmiar==ntohs((uint16_t)0u))
   {
      free(bufor);
      bufor=(int8_t *)malloc(rozmiarACK+dlugoscListyKatalogow());
#ifdef DEBUG
      fprintf(stderr,"dlugoscListyKatalogow=%u",dlugoscListyKatalogow());
#endif
      ack=(struct pakietACK *)bufor;
      dane=bufor+rozmiarACK;
      aktualne=dane;
      for(element=katalogi;element!=NULL;element=element->nastepny)
      {
	 strcpy(aktualne,element->katalog.d_name);
	 aktualne+=strlen(element->katalog.d_name);
	 *aktualne='\n';
	 ++aktualne;
      }

      ack->naglowek.rozmiar=htons((int16_t)(aktualne-dane)+sizeof(uint32_t));
      ack->naglowek.operacja=OP_ACK;
      ack->naglowek.opcje=0u;
      ack->naglowek.ktory=htonl((uint32_t)ktory);
      ack->ktory=htonl((uint32_t)ktoryTam-1);
#ifdef DEBUG
   fprintf(stderr,"rozmiar=%u\n",ack->naglowek.rozmiar);
#endif
      wynik=wyslijKomunikat(dw->komunikator,NULL,bufor);
      if(wynik==-1)
      {
	 pthread_exit(NULL);
      }
      ++ktory;
   }
   else
   {
      free(bufor);
      fprintf(stderr,"Błędna operacja. Oczekiwano operacji LIST.\n");
      pthread_exit(NULL);
   }

   wynik=przyjmijKomunikat(dw->komunikator,&adres,&bufor);
   if(wynik==-1)
   {
      pthread_exit(NULL);
   }
   ++ktoryTam;
   nag=(struct naglowek *)bufor;
   dane=bufor+rozmiarNaglowka;

   if(nag->operacja==OP_GET)
   {
      napis=(int8_t *)malloc(ntohs(nag->rozmiar)+1);
      memcpy(napis,dane,ntohs(nag->rozmiar));
      napis[ntohs(nag->rozmiar)]='\0';
#ifdef DEBUG
   fprintf(stderr,"Otwieram plik\n");
#endif
      plik=fopen(napis,"rb");
      if(plik==NULL)
      {
	 perror("fopen");
	 pthread_exit(NULL);
      }
      free(bufor);

#ifdef DEBUG
   fprintf(stderr,"Szukam końca pliku\n");
#endif
      wynik=fseek(plik,0L,SEEK_END);
      if(wynik==-1)
      {
	 fclose(plik);
	 perror("fseek");
	 pthread_exit(NULL);
      }
#ifdef DEBUG
   fprintf(stderr,"Przechodzę do końca pliku\n");
#endif
      dlugoscPliku=ftell(plik);
      if(dlugoscPliku==-1L)
      {
	 fclose(plik);
	 perror("ftell");
	 pthread_exit(NULL);
      }
#ifdef DEBUG
   fprintf(stderr,"Wracam na początek pliku\n");
#endif
      wynik=fseek(plik,0L,SEEK_SET);
      if(wynik==-1)
      {
	 fclose(plik);
	 perror("fseek");
	 pthread_exit(NULL);
      }

      bufor=(int8_t *)malloc(rozmiarACK+sizeof(uint32_t));
      if(bufor==NULL)
      {
	 fclose(plik);
	 fprintf(stderr,"Nie można zaalokować pamięci\n");
	 pthread_exit(NULL);

      }
      dane=bufor+rozmiarACK;
      ack=(struct pakietACK *)bufor;

      ack->naglowek.rozmiar=htons(2*sizeof(uint32_t));
      ack->naglowek.operacja=OP_ACK;
      ack->naglowek.opcje=(int8_t)0u;
      ack->naglowek.ktory=htonl(ktory);
      ack->ktory=htonl(ktoryTam-1);
      {
	 uint32_t *l=(uint32_t *)dane;
	 *l=htonl(dlugoscPliku%rozmiar?dlugoscPliku/rozmiar+1:dlugoscPliku/rozmiar);
#ifdef DEBUG
   fprintf(stderr,"WYSYŁAM %u pakietów\n",ntohl(*l));
#endif
      }
      wynik=wyslijKomunikat(dw->komunikator,NULL,bufor);
      if(wynik==-1)
      {
	 fclose(plik);
	 pthread_exit(NULL);
      }
      ++ktory;

#ifdef DEBUG
   fprintf(stderr,"Za chwilę wejdę do pętli wysyłającej plik\n");
#endif
      while(!feof(plik))
      {
#ifdef DEBUG
	 fprintf(stderr,"iteracja...\n");
#endif
	 bufor=(int8_t *)malloc(rozmiarNaglowka+rozmiar);
	 nag=(struct naglowek *)bufor;
	 dane=bufor+rozmiarNaglowka;

	 nag->rozmiar=htons(rozmiar);
	 nag->operacja=OP_DAT;
	 nag->opcje=(int8_t)0u;
	 nag->ktory=htonl(ktory);

	 zapisane=fread(dane,sizeof(int8_t),rozmiar,plik);
	 if(zapisane==0)
	 {
	    break;
	 }
#ifdef DEBUG
	 fprintf(stderr,"Wczytano %u bajtów z pliku\n",zapisane);
#endif

	 nag->rozmiar=htons(zapisane);

#ifdef DEBUG
   fprintf(stderr,"Zaraz wyslijKomunikat()\n");
#endif
	 wynik=wyslijKomunikat(dw->komunikator,NULL,bufor);
	 if(wynik==-1)
	 {
	    fclose(plik);
	    pthread_exit(NULL);
	 }
	 ++ktory;
#ifdef DEBUG
   fprintf(stderr,"Koniec wysylanie wyslijKomunikat()\n");
#endif

#ifdef DEBUG
   fprintf(stderr,"Wysłałem pakiet, czekam na potwierdzenie\n");
#endif
	 wynik=przyjmijKomunikat(dw->komunikator,&adres,&bufor);
	 if(wynik==-1)
	 {
	    fclose(plik);
	    pthread_exit(NULL);
	 }
#ifdef DEBUG
   fprintf(stderr,"Otrzymałem potwierdzenie\n");
#endif
	 ++ktoryTam;
	 nag=(struct naglowek *)bufor;
	 dane=bufor+rozmiarNaglowka;

	 if(nag->operacja!=OP_ACK)
	 {
	    /*ERROR*/
	    /*Jeżeli nie dostałem potwierdzenia*/
	 }
      }

#ifdef DEBUG
      fprintf(stderr,"OK! Plik został wysłany\n");
#endif
      fclose(plik);
   }
   else
   {
      free(bufor);
      fprintf(stderr,"Błędna operacja. Oczekiwano operacji GET.\n");
      pthread_exit(NULL);
   }

#ifdef DEBUG
   fprintf(stderr,"Czekam za zakończenie\n");
#endif
   wynik=przyjmijKomunikat(dw->komunikator,&adres,&bufor);
   if(wynik==-1)
   {
      pthread_exit(NULL);
   }
   ++ktoryTam;
   nag=(struct naglowek *)bufor;
   dane=bufor+rozmiarNaglowka;


   if(nag->operacja!=OP_QUIT)
   {
      free(bufor);
      fprintf(stderr,"Błędna operacja. Oczekiwano operacji QUIT.\n");
      pthread_exit(NULL);
   }
   free(bufor);

#ifdef DEBUG
   fprintf(stderr,"Wysyłam potwierdzenie zakończenia\n");
#endif
   wynik=wyslijPZ(dw->komunikator,OP_ACK,ktory,ktoryTam-1);
   close(dw->komunikator->gniazdo);
   free(dw->komunikator);
   free(d);

#ifdef DEBUG
   fprintf(stderr,"Koniec\n");
   fflush(stderr);
#endif
   pthread_exit(NULL);
}

int stworzGniazdoNasluchujace(int *gniazdo)
{
   unsigned int i;
   int wynik;
   struct addrinfo *adresy, *wskaznik;
   struct addrinfo wskazowki;

   memset(&wskazowki, 0, sizeof(struct addrinfo));
   //wskazowki.ai_family = AF_UNSPEC;
   wskazowki.ai_family = AF_INET;
   wskazowki.ai_socktype = SOCK_DGRAM;
   wskazowki.ai_flags = AI_PASSIVE;

   wynik=getaddrinfo(NULL,PORT,&wskazowki,&adresy);
   if(wynik!=0)
   {
      fprintf(stderr, "getaddrinfo: %s\n",gai_strerror(wynik));
      return -1;
   }
   for(wskaznik = adresy; wskaznik != NULL; wskaznik=wskaznik->ai_next)
   {
      *gniazdo=socket(
	    wskaznik->ai_family,
	    wskaznik->ai_socktype,
	    wskaznik->ai_protocol);
      if(*gniazdo==-1)
      {
	 perror("socket");
	 continue;
      }
      wynik=bind(*gniazdo,wskaznik->ai_addr,wskaznik->ai_addrlen);
      if(wynik==-1)
      {
	 close(*gniazdo);
	 perror("bind");
	 continue;
      }
      freeaddrinfo(adresy);
      return 0;
   }
   freeaddrinfo(adresy);
   return -1;
}

int inicjalizujCentrale(int gniazdo, SSL * ssl, BIO * bio)
{
   struct komunikator *nowy;
   int wynik;

   nowy=(struct komunikator *)malloc(sizeof(struct komunikator));
   if(nowy==NULL)
   {
      fprintf(stderr,"Nie udało się zaalokować pamięci\n");
      return -1;
   }
   nowy->adres=NULL;
   nowy->gniazdo=gniazdo;
   wynik=dodajKomunikator(nowy);
   if(wynik==-1)
   {
      return -1;
   }
   return 0;
}

int stworzWatek(struct sockaddr_storage *adres, struct komunikator **kom)
{
   int wynik;
   int gniazda[2];
   struct daneWatku *dane;
   struct komunikator *wkom;
   struct sockaddr_storage *a;
   pthread_t w;

   *kom=(struct komunikator *)malloc(sizeof(struct komunikator));
   if(*kom==NULL)
   {
      fprintf(stderr,"Nie udało się zaalokować  pamięci\n");
      return -1;
   }
   /* należy dorobić free gdzieś w kodzie */
   wkom=(struct komunikator *)malloc(sizeof(struct komunikator));
   if(wkom==NULL)
   {
      free(*kom);
      fprintf(stderr,"Nie udało się zaalokować  pamięci\n");
      return -1;
   }
   /* należy dorobić free gdzieś w kodzie */
   a=(struct sockaddr_storage *)malloc(sizeof(struct sockaddr_storage));
   if(a==NULL)
   {
      free(*kom);
      free(wkom);
      fprintf(stderr,"Nie udało się zaalokować  pamięci\n");
      return -1;
   }
   /* należy dorobić free gdzieś w kodzie */
   memcpy(a,adres,sizeof(struct sockaddr_storage));
   wynik=socketpair(AF_UNIX,SOCK_DGRAM,0,gniazda);
   if(wynik==-1)
   {
      free(*kom);
      free(wkom);
      free(a);
      perror("socketpair");
      return -1;
   }

   dane=malloc(sizeof(struct daneWatku));
   if(dane==NULL)
   {
      free(*kom);
      free(wkom);
      free(a);
      close(gniazda[0]);
      close(gniazda[1]);
      fprintf(stderr,"Nie udało się zaalokować  pamięci\n");
      return -1;
   }
   (*kom)->adres=adres;
   (*kom)->gniazdo=gniazda[0];
   wkom->adres=a;
   wkom->gniazdo=gniazda[1];
   dane->komunikator=wkom;

   wynik=pthread_create(&w,NULL,watek,dane);
   if(wynik!=0)
   {
      free(*kom);
      free(wkom);
      free(a);
      close(gniazda[0]);
      close(gniazda[1]);
      return -1;
   }
   wynik=pthread_detach(w);
   if(wynik!=0)
   {
      free(*kom);
      free(wkom);
      free(a);
      close(gniazda[0]);
      close(gniazda[1]);
      pthread_kill(w,SIGTERM);
      return -1;
   }
   wynik=dodajKomunikator(*kom);
   if(wynik==-1)
   {
      free(*kom);
      free(wkom);
      free(a);
      close(gniazda[0]);
      close(gniazda[1]);
      pthread_kill(w,SIGTERM);
      return -1;
   }
   return 0;
}

int pobierzWyslij(struct komunikator *k)
{
   int8_t *bufor;
   struct sockaddr_storage *adres;
   struct komunikator *kom;
   int wynik;

#ifdef DEBUG
   fprintf(stderr,"Wchodze do funkcji pobierzWyslij\n");
   fprintf(stderr,"Za chwile przyjme komunikat\n");
#endif
   wynik=przyjmijKomunikat(k,&adres,&bufor);
   if(wynik==-1)
   {
      return -1;
   }
#ifdef DEBUG
   fprintf(stderr,"Za chwile bede szukal komunikatora\n");
#endif
   wynik=znajdzKomunikator(adres,&kom);
   if(wynik==-1)
   {

#ifdef DEBUG
      fprintf(stderr,"Za chwile utworze watek\n");
#endif
      wynik=stworzWatek(adres,&kom);
      if(wynik==-1)
      {
	 return -1;
      }
   }
#ifdef DEBUG
   fprintf(stderr,"Za chwile wysle komunikat\n");
#endif
   wynik=wyslijKomunikat(kom,k->adres,bufor);
   if(wynik==-1)
   {
      return -1;
   }
   return 0;
}


int main(int argc, char *argv[])
{
   int gniazdo;
   int wyjscie;
   unsigned int i;
   struct komunikator **k;
   unsigned int ilosc;

   SSL_library_init();
   SSL_load_error_strings();

    FILE* paramfile=fopen("./dh_param_1024.pem","r");
    if(paramfile==NULL){
    fprintf(stderr,"Blad otwarcia pliku\n");
    }

    DH* dh_1024 = PEM_read_DHparams(paramfile,NULL,NULL,NULL);

    if(dh_1024==NULL){
    fprintf(stderr,"Blad odczytu pliku\n");
    }

    fclose(paramfile);

    #ifdef DEBUG
   fprintf(stderr,"Wczytuję pliki w katalogu\n");
#endif

   wyjscie=wczytajKatalogi();
   if(wyjscie==-1)
   {
      return -1;
   }

#ifdef DEBUG
   fprintf(stderr,"Otwieram gniazdo nasłuchujące\n");
#endif

   wyjscie=stworzGniazdoNasluchujace(&gniazdo);
   if(wyjscie==-1)
   {
      return -1;
   }

   BIO *conn = BIO_new_dgram(gniazdo,BIO_NOCLOSE);
   if(conn==NULL)
   {
       fprintf(stderr,"Blad tworzenia bio\n");

   }


   SSL_CTX *ctx = SSL_CTX_new(DTLSv1_server_method());
   if (ctx==NULL)
   {
       ERR_print_errors_fp(stderr);
   }

   SSL_CTX_set_read_ahead(ctx,1);
   SSL_CTX_set_cipher_list(ctx,"HIGH:MEDIUM:aNULL");
   SSL_CTX_set_tmp_dh(ctx,dh_1024);

   SSL *ssl = SSL_new(ctx);
   if (ssl==NULL)
   {
       fprintf(stderr,"Blad ssl\n");
   }

   //SSL_set_bio(ssl,conn,conn);
   //SSL_set_accept_state(ssl);

/*
    char buf[256] = {0};
	//int err = SSL_read(ssl, buf, 256);
	if (err <= 0) {
		err = SSL_get_error(ssl, err);
		fprintf(stderr, "SSL_read: error %d\n", err);

	}
	fprintf(stderr, "%s\n", buf);
   //fprintf(stderr,"ssl");*/


#ifdef DEBUG
   /*Sprawdziłem, na tym etapie jest poprawnie otworzone gniazdo*/
   /*
   struct sockaddr_storage adres;
   uint8_t bufor[MAX_PAKIET];
   odbierzpakiet(gniazdo,&adres,bufor);
   */






   fprintf(stderr,"Inicjalizuję centralę\n");
#endif
   wyjscie=inicjalizujCentrale(gniazdo,ssl,conn);
   if(wyjscie==-1)
   {
      return -1;
   }

#ifdef DEBUG
   wypiszKomunikatory();
   fprintf(stderr,"Wchodzę do głównej pętli\n");
#endif
petlaglowna:
#ifdef DEBUG
   fprintf(stderr,"Zaraz poczekam na komunikaty\n");
#endif
   wyjscie=oczekujNaKomunikaty(&k,&ilosc);
   if(wyjscie==-1)
   {
      return -1;
   }
#ifdef DEBUG
   fprintf(stderr,"Lista komunikatorów po oczekiwaniu\n");
   wypiszKomunikatory();
   fprintf(stderr,"Coś czeka\n");
#endif
   for(i=0;i<ilosc;++i)
   {
#ifdef DEBUG
   fprintf(stderr,"Przetwarzam\n");
#endif
    k[i]->bio=conn;
    k[i]->ssl=ssl;
      wyjscie=pobierzWyslij(k[i]);
      if(wyjscie==-1)
      {
	 return -1;
      }
   }

#ifdef DEBUG
   fprintf(stderr,"Pobrałem i wysłałem\n");
#endif
   goto petlaglowna;

    SSL_free(ssl); /* free conn too *//*@ \label{ds:clean:beg} @*/
	SSL_CTX_free(ctx);
	DH_free(dh_1024);
   return EXIT_SUCCESS;
}
   /*
    * -1. Otwórz gniazdo nasłuchujące
    * 2. Zaakceptuj połączenie i utwórz nowy wątek
    * 3. Czekaj na polecenia
    * 4. Jeśli dostaniesz SET to ustaw parametry transmisji
    * 5. Jeśli dostaniesz LIST wyślij listę plików
    * 6. Jeśli dostaniesz QUIT zamknij połączenie
    * 7. Jeśli dostaniesz GET nazwa_pliku zwróć numer portu
    * 8. Prześlij plik
    */

